
"""
 * Copyright 2009 Yogi KENT
 * 
 * This file is main part of Sotetsu.
 * 
 * Sotetsu is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 * 
 * Sotetsu is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.

"""

import threading
import ossaudiodev
from sotetsu import madsimple
#import madsimple
import datetime

AUDIODEV = "/dev/dsp"


class Player:
	def __init__(self):
		"""Plays mp3 files on different thread.

		mp3 file objects are generated by playlist iterator.
		"""
		#threading.Thread.__init__(self, name="player")
		self.cond = threading.Condition()
		self.thread = None

		# flags
		self.nextflg = False
		self.pauseflg = False
		self.finishflg = True

		# callback functions
		self.setCallbackonFinish(None)
		self.setCallbackonNext(None)
		self.setClockCallback(None)

		self.dev = None

	def __devopen(self):
		# device initialization
		self.dev = ossaudiodev.open(AUDIODEV, "w")
		self.dev.setfmt(ossaudiodev.AFMT_S16_LE)
		self.dev.channels(2)
		self.dev.speed(44100)

	def __enter__(self):
		return self
	def __exit__(self, exc_type, exc_val, exc_tb):
		"""it should be changed soon."""
		self.close()
		return False

	def setPlaylist(self, playlist):
		self.playlist = playlist
	
	def play(self):
		if not self.dev:
			self.__devopen()
		if self.thread!=None:
			self.stop()
		self.nextflg = False
		self.pauseflg = False
		self.finishflg = False

		# This Class cannot inherit Thread.  because the play method can be
		# called two or more times after stop method was called.
		self.thread = threading.Thread(target=self.ThreadRunner, name="player", args=())
		self.thread.start()

	def setCallbackonFinish(self, callback, arg=None):
		"""set callback function which is called at player thread finish."""
		self.onfinish_arg = arg
		if callback==None:
			self.onfinish = lambda x : None
		else:
			self.onfinish = callback
	def setCallbackonNext(self, callback, arg=None):
		"""set callback function which is called at player is about to play next data."""
		self.onnext_arg = arg
		if callback==None:
			self.onnext = lambda x : None
		else:
			self.onnext = callback
	def setClockCallback(self, callback, arg=None):
		"""set callback function which is called at each frame data is decoded."""
		self.onclock_arg = arg
		if callback==None:
			self.onclock = lambda x,y : None
		else:
			self.onclock = callback

	def next(self):
		self.cond.acquire()
		self.nextflg = True
		self.cond.notify()
		self.cond.release()
	def stop(self):
		self.cond.acquire()	# Lock
		self.nextflg = True
		self.finishflg = True
		self.cond.notify()
		self.cond.release()	# UnLock
		self.thread.join()
		self.thread = None
	def pause(self):
		self.cond.acquire()
		self.pauseflg = True
		self.cond.release()
	def unpause(self):
		self.cond.acquire()
		self.pauseflg = False
		self.cond.notify()
		self.cond.release()
	def close(self):
		if not self.finishflg: self.stop()
		self.dev.close()
	def isPlaying(self):
		if not self.thread: return False
		return self.thread.is_alive()
	def getPlaylist(self):
		return self.playlist


	# for Threading Methods
	def ThreadRunner(self):
		"""invoked by Thread object"""
		for mp3 in self.playlist:
			self.time = datetime.timedelta(0)
			self.onnext(self.onnext_arg)
			self.decoder = madsimple.Decoder(mp3, self.dev, 5000)
			self.decoder.run(header=self.header, output=self.output, error=self.error)
			self.decoder.finish()
			del self.decoder
			if self.finishflg:
				break
			else:
				self.nextflg=False
		self.finishflg = True
		self.onfinish(self.onfinish_arg)

	def output(self, dev, pcm_data):
		dev.write(pcm_data)
		return madsimple.FLOW_CONTINUE

	def header(self, dev, time):
		"""This is callback function for madsimple.Decoder.

		time is datetime.time object.
		"""

		# stop check
		if self.nextflg: return madsimple.FLOW_STOP
		# pauseflg check
		self.cond.acquire()
		while self.pauseflg:
			# wait until stop or unpause methods are called.
			self.cond.wait()
			# nextflg check again!
			if self.nextflg:
				self.cond.release()
				return madsimple.FLOW_STOP
			# yes, we have to check pauseflg again,
			# because the flag might be setted again before this thread wake.
		self.cond.release()

		# callbackfunction.
		self.time += time
		self.onclock(self.time, self.onclock_arg)

		#print("{0:2d}sec\r".format(time.second))
		return madsimple.FLOW_CONTINUE

	def error(self, dev, data):
		pass


